// @ts-nocheck
import * as _xlsx from 'xlsx'

/**
 * @typedef {ArrayBuffer | Array<Array<string> | Record<string, string>>} XlsxInputContent 输入的xlsx内容
 * @typedef {Record<string, any>[]} XlsxNormalContent 标准化后的xlsx内容
 */

export class Xlsx {
  get content() {
    return this._content
  }
  /**
   * @type {XlsxNormalContent}
   * @private
   */
  _content = []
  /**
   * 使用网络文件创建xlsx
   *
   * @param {string} url
   * @returns {Promise<Xlsx>}
   */
  static async createXlsxByFetchFile(url) {
    const x = new Xlsx()
    await x.fetchFileInput(url)
    return x
  }
  /**
   * 使用原始内容创建xlsx
   *
   * @param {XlsxInputContent} rawContent
   * @returns {Xlsx}
   */
  static createXlsx(rawContent) {
    const x = new Xlsx()
    x.input(rawContent)
    return x
  }
  /**
   * 原始内容输入
   *
   * @param {XlsxInputContent} rawContent
   */
  input(rawContent) {
    this._content = normalInput(rawContent)
  }
  /**
   * 通过浏览器选择获取文件输入
   */
  async chooseFileInput() {
    return new Promise((resolve, reject) => {
      const el = document.createElement('input')
      el.setAttribute('type', 'file')
      el.onchange = async (e) => {
        const file = e.target.files[0]
        this.input(await file.arrayBuffer())
        resolve()
        dispose()
      }
      el.onerror = (err) => {
        reject(new Error('选择文件失败'))
        dispose()
      }
      document.body.appendChild(el)
      el.click()

      function dispose() {
        document.body.removeChild(el)
      }
    })
  }
  /**
   * 通过网络获取文件输入
   *
   * @param {string} url
   */
  async fetchFileInput(url) {
    await fetch(url)
      .then((v) => v.arrayBuffer())
      .then(this.input.bind(this))
  }
  /**
   *
   * @param {string?} name
   * @param {{header?: Array<string | {label: string, field: string, handler?: (row) => string}>}?} options
   */
  output(name, options) {
    const outputFileName = name ?? 'output.xlsx'
    let _outputHeader = options?.header
    if (!_outputHeader && !!this._content[0]) {
      _outputHeader = Object.keys(this._content[0])
    }
    let outputHeader = []
    if (_outputHeader) {
      outputHeader = _outputHeader.map((v) => {
        if (typeof v === 'string') {
          return { label: undefined, field: v }
        } else {
          return v
        }
      })
    }
    // 格式化后的导出数据
    const outputData = this._content.map((obj) => {
      return (
        outputHeader
          // 按照导出head填充数据
          .map((hv) => {
            const o = { field: hv.field, value: undefined }
            if (hv.handler) {
              o.value = hv.handler(obj)
            } else {
              o.value = obj[hv.field]
            }
            return o
          })
          // 优化数字
          .map((o) => {
            if (typeof o.value === 'number') {
              o.value = normalDigit(o.value)
            }
            return o
          })
          // 填充数据
          .reduce((to, it) => {
            to[it.field] = it.value
            return to
          }, {})
      )
    })
    const headFormatKeyMapKey = outputHeader.reduce((to, it) => {
      to[it.field] = getOutputKeyByHeaderItem(it)
      return to
    }, {})
    const sheetJson = _xlsx.utils.json_to_sheet(
      outputData.map((item) => {
        return Object.fromEntries(
          Object.entries(item).map(([key, value]) => {
            return [headFormatKeyMapKey[key], value]
          })
        )
      }),
      {
        header: outputHeader.map(getOutputKeyByHeaderItem)
      }
    )
    _xlsx.writeFile(
      {
        SheetNames: ['sheet1'],
        Sheets: {
          sheet1: sheetJson
        }
      },
      outputFileName
    )
  }
}

function isObject(v) {
  return Object.prototype.toString.call(v) === '[object Object]'
}
/**
 * 标准化输入
 *
 * @param {XlsxInputContent} rawContent
 * @returns {XlsxNormalContent}
 */
function normalInput(rawContent) {
  /** @type {_xlsx.WorkSheet} */
  let sheet1
  if (rawContent instanceof ArrayBuffer) {
    const workbook = _xlsx.read(rawContent, { type: 'buffer' })
    sheet1 = workbook.Sheets[workbook.SheetNames[0]]
  } else if (rawContent instanceof Array) {
    if (rawContent.length === 0) {
      sheet1 = convertArray(rawContent)
    } else if (Array.isArray(rawContent[0])) {
      sheet1 = convertArray(rawContent)
    } else if (isObject(rawContent[0])) {
      const head = Object.keys(rawContent[0])
      const body = rawContent.map((o) => {
        const b = []
        for (let [key, value] of Object.entries(o)) {
          const i = head.indexOf(key)
          b[i] = value
        }
        return b
      })
      sheet1 = convertArray([head, ...body])
    } else {
      errorType()
    }
  } else if (typeof rawContent === 'string') {
    try {
      const data = JSON.parse(rawContent)
      sheet1 = convertArray(data)
    } catch (error) {
      errorType()
    }
  } else {
    errorType()
  }
  const content = _xlsx.utils.sheet_to_json(sheet1)
  return content.map(getNormalItemByRawContentItem)

  function convertArray(rawContent) {
    // if (rawContent.length === 0) throw new Error("xlsx输入的js数据不正确")

    return _xlsx.utils.aoa_to_sheet(rawContent)
  }
  function errorType() {
    throw new Error('xlsx导入类型错误')
  }
}

/** 根据head配置获取导出key */
function getOutputKeyByHeaderItem(obj) {
  if (obj.label) {
    if (obj.field) {
      return `${obj.label}(${obj.field})`
    } else {
      return obj.label
    }
  } else {
    return obj.field
  }
}

/** 根据输入原始item获取标准化item */
function getNormalItemByRawContentItem(obj) {
  return Object.fromEntries(
    Object.entries(obj).map(([key, value], index) => {
      if (!key) return [`field${index + 1}`, value]
      return [key.replace(/^.+\((.+)\)$/i, '$1'), value]
    })
  )
}

/**
 * 标准化数字
 *
 * @param {number} value
 * @returns {string}
 */
function normalDigit(value) {
  const str = value.toString()
  if (/e\+/i.test(str)) {
    const r = BigInt(value).toString()
    return r
  } else if (/e\-/i.test(str)) {
    const r = '0' + (value + 1).toString().substr(1)
    return r
  }
  return value.toString()
}
